package world

import (
	"MetroBuilderGameJam/gameEngine/geom"
	"fmt"
	"reflect"
)

type StationType int

const (
	StationTypeCircle StationType = iota
	StationTypeTriangle
	StationTypeSquare
	StationTypePentagon
	StationTypeStar
	StationTypeCross
)

type RoadSlotSide int

const (
	Right RoadSlotSide = iota * 45
	BottomRight
	Bottom
	BottomLeft
	Left
	TopLeft
	Top
	TopRight
)

func NewStation(shape StationType, position geom.Point) *Station {
	station := NewStationProject(shape)
	station.position = &position

	return station
}

func NewStationProject(shape StationType) *Station {
	return &Station{
		shape:      shape,
		roadslots:  map[RoadSlotSide][]RailRoadBranch{},
		passengers: make([]*Passenger, 0, 10),
	}
}

type Station struct {
	shape      StationType
	position   *geom.Point
	roadslots  map[RoadSlotSide][]RailRoadBranch
	passengers []*Passenger
}

func (s *Station) Position() *geom.Point {
	return s.position
}

func (s *Station) Shape() StationType {
	return s.shape
}

func (s *Station) SlotIndexFor(b RailRoadBranch) (int, error) {
	if b.FromStation() != s && b.ToStation() != s {
		return 0, fmt.Errorf("branch is not connected to station")
	}
	for _, slot := range s.roadslots {
		for i, branch := range slot {
			if reflect.DeepEqual(branch, b) {
				return i, nil
			}
		}
	}
	return 0, fmt.Errorf("branch is not connected to station")
}

func (s *Station) TryConnectToBranch(b RailRoadBranch) *BranchErrorAt {
	if b.FromStation() != s && b.ToStation() != s {
		return nil
	}
	_, isBranchEdge := b.(*BranchEdge)
	edges := map[RoadSlotSide]RailRoadBranch{}
	tempslots := map[RoadSlotSide][]RailRoadBranch{}
	for side, slots := range s.roadslots {
		for i, branch := range slots {
			if reflect.DeepEqual(branch, b) {
				continue
			}
			_, isBranchProject := branch.(*BranchProject)
			if _, ok := branch.(*BranchProject); isBranchProject && ok {
				continue
			}
			if edge, ok := branch.(*BranchEdge); !isBranchEdge && ok {
				edges[side] = edge
				continue
			}
			tempslots[side] = append(tempslots[side], slots[i])
		}
	}
	//fmt.Printf("TryConnectToBranch, s.shape: %d, s.position: %v, b: %T, rr.Color: %v\n", s.shape, s.position, b, b.RailRoad().IsReal())
	//fmt.Printf("TryConnectToBranch, tempslots: %v\n", tempslots)
	newSide := s.calcSideForBranch(b)
	newSide = s.calcSideForBranch(b)
	//fmt.Printf("TryConnectToBranch, new side: %d\n", newSide)
	if s.calcSlotWeight(tempslots[newSide], b) > 3 {
		if !isBranchEdge {
			return &BranchErrorAt{Position: *s.position, Reason: "max slots reached"}
		}
		freeSide := s.findClosestFreeSlotForEdge(newSide)
		if freeSide == newSide {
			return &BranchErrorAt{Position: *s.position, Reason: "max slots reached"}
		}
		newSide = freeSide
	}
	tempslots[newSide] = append(tempslots[newSide], b)
	if len(edges) > 0 {
		replacedEdges := map[RoadSlotSide]RailRoadBranch{}
		for side, edge := range edges {
			if s.calcSlotWeight(tempslots[side], edge) == 3 {
				tempslots[side] = append(tempslots[side], edge)
			} else {
				replacedEdges[side] = edge
			}
		}
		for side, edge := range replacedEdges {
			freeSide := s.findClosestFreeSlotForEdge(side)
			if freeSide == side {
				return &BranchErrorAt{Position: *s.position, Reason: "max slots reached"}
			}
			tempslots[freeSide] = append(tempslots[freeSide], edge)
		}
	}

	s.roadslots = tempslots

	return nil
}

func (s *Station) calcSideForBranch(b RailRoadBranch) RoadSlotSide {
	if s.position == nil {
		return Bottom
	}

	from := *b.From()
	bendPosition := b.CalcBendPosition()
	if bendPosition == from && *s.position == from {
		bendPosition = *b.To()
	}
	angle := bendPosition.AsVec2().Sub(s.Position().AsVec2()).ToRadian()
	side := angleToSide(angle)
	//fmt.Printf("Angle: %f, side for branch: %d\n", angle, side)
	return side
}

func (s *Station) UpdateBranchesSlots(branchesState []RailRoadBranch) {
	s.roadslots = map[RoadSlotSide][]RailRoadBranch{}
	for _, b := range branchesState {
		s.TryConnectToBranch(b)
	}
}

func (s *Station) GetSlotForBranch(b RailRoadBranch) (RoadSlotSide, int) {
	for side, slot := range s.roadslots {
		for i, branch := range slot {
			if reflect.DeepEqual(branch, b) {
				return side, i
			}
		}
	}
	return 0, -1
}

func (s *Station) calcSlotWeight(branches []RailRoadBranch, newBranch RailRoadBranch) int {
	w := 0
	for _, b := range branches {
		w += s.getSlotWeight(b)
	}
	if newBranch != nil {
		w += s.getSlotWeight(newBranch)
	}
	return w
}

func (s *Station) getSlotWeight(b RailRoadBranch) int {
	switch b.(type) {
	case *BranchProject:
		return b.(*BranchProject).weight
	case *BranchCompleted:
		return 1
	case *BranchEdge:
		return 3
	}
	return 0
}

func (s *Station) findClosestFreeSlotForEdge(side RoadSlotSide) RoadSlotSide {
	diff := 45
	for diff < 180 {
		newSide := RoadSlotSide(geom.Degrees(int(side) + diff).Normalize())
		if _, ok := s.roadslots[newSide]; !ok || s.calcSlotWeight(s.roadslots[newSide], nil) == 0 {
			return newSide
		}
		newSide = RoadSlotSide(geom.Degrees(int(side) - diff).Normalize())
		if _, ok := s.roadslots[newSide]; !ok || s.calcSlotWeight(s.roadslots[newSide], nil) == 0 {
			return newSide
		}
		diff += 45
	}
	return side
}

func (s *Station) AddPassenger(p *Passenger) {
	if p.Shape() == s.shape {
		return
	}
	s.passengers = append(s.passengers, p)
}

func (s *Station) Passengers() []*Passenger {
	return s.passengers
}

func (s *Station) GivePassenger(index int) *Passenger {
	if len(s.passengers) <= index {
		return nil
	}
	p := s.passengers[index]
	s.passengers = append(s.passengers[:index], s.passengers[index+1:]...)
	return p
}

func angleToSide(angle geom.Radian) RoadSlotSide {
	d := int(angle.ToDegrees().Normalize())

	return RoadSlotSide(int((d+15)/45) * 45)
}
